/*
 * MIT License
 *
 * Copyright (c) 2023 北京凯特伟业科技有限公司
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package com.je.meta.service.func.impl;

import com.alibaba.fastjson2.JSONObject;
import com.je.common.base.DynaBean;
import com.je.common.base.exception.PlatformException;
import com.je.common.base.exception.PlatformExceptionEnum;
import com.je.common.base.service.CommonService;
import com.je.common.base.service.MetaService;
import com.je.common.base.service.rpc.BeanService;
import com.je.common.base.util.DateUtils;
import com.je.common.base.util.StringUtil;
import com.je.ibatis.extension.conditions.ConditionsWrapper;
import com.je.meta.service.func.MetaCodeGenService;
import com.je.meta.util.RedisLockUtils;
import com.xxl.job.core.handler.annotation.XxlJob;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
import java.util.stream.Collectors;

@Service
public class MetaCodeGenServiceImpl implements MetaCodeGenService {

    private static Logger logger = LoggerFactory.getLogger(MetaCodeGenServiceImpl.class);
    private static final String LOCK_KEY = "redisLock_";

    private String byMonth = "MONTH";
    private String byYear = "YEAR";
    private String byWeek = "WEEK";
    private String byDay = "DAY";

    @Autowired
    private MetaService metaService;
    @Lazy
    @Autowired
    private CommonService commonService;

    @Override
    @Transactional
    @XxlJob("cleanCodeGenerator")
    public void cleanCodeGenerator() {
        logger.debug("开始执行业务编码重置操作");
        Calendar c = Calendar.getInstance();
        boolean firstDayOfMonth = DateUtils.isFirstDayOfMonth(c);
        boolean firstDayOfWeek = DateUtils.isFirstDayOfWeek(c);
        boolean firstDayOfYear = DateUtils.isFirstDayOfYear(c);

        List<DynaBean> list = metaService.select(ConditionsWrapper.builder().table("JE_CORE_CODEGENSEQ"));
        for (DynaBean seq : list) {
            String emptyType = seq.getStr("CODEGENSEQ_EMPTYTYPE");
            if (byMonth.equals(emptyType) && firstDayOfMonth) {
                logger.info("当前日期：" + DateUtils.formatDate(c.getTime()) + "->执行月初重置操作");
                seq.set("CODEGENSEQ_CODEVALUE", 0);
            }
            if (byYear.equals(emptyType) && firstDayOfYear) {
                logger.info("当前日期：" + DateUtils.formatDate(c.getTime()) + "->执行年初重置操作");
                seq.set("CODEGENSEQ_CODEVALUE", 0);
            }
            if (byWeek.equals(emptyType) && firstDayOfWeek) {
                logger.info("当前日期：" + DateUtils.formatDate(c.getTime()) + "->执行周一重置操作");
                seq.set("CODEGENSEQ_CODEVALUE", 0);
            }
            if (byDay.equals(emptyType)) {
                logger.info("当前日期：" + DateUtils.formatDate(c.getTime()) + "->执行每日重置操作");
                seq.set("CODEGENSEQ_CODEVALUE", 0);
            }
            metaService.update(seq);
        }
        logger.debug("业务编码重置操作执行结束");
    }

    @Override
    @Transactional
    public void cleanCodeValueByType(String emptyType) {
        metaService.executeSql("UPDATE JE_CORE_CODEGENSEQ  SET CODEGENSEQ_CODEVALUE = 0 WHERE CODEGENSEQ_EMPTYTYPE = {0}", emptyType);
    }

    @Override
    @Transactional
    public String getSeq(JSONObject infos, String fieldName, String codeBase, String step, String cycle, Integer length) {
        return getSeq(infos, fieldName, codeBase, step, cycle, length, "");
    }

    @Override
    @Transactional
    public String getSeq(JSONObject infos, String fieldName, String codeBase, String step, String cycle, Integer length, String zhId) {
        //自增字段配置
        String type = infos.getString("TYPE");
        String tableCode = infos.getString("TABLECODE");
        String tableName = infos.getString("TABLENAME");
        String funcCode = infos.getString("FUNCCODE");
        String funcId = infos.getString("FUNCID");
        String funcName = infos.getString("FUNCNAME");

        //自增序列唯一标识
        String seqKey = "%s_%s_%s";
        //查询条件
        ConditionsWrapper builder = ConditionsWrapper.builder();
        if ("TABLE".equals(type)) {
            seqKey = String.format("%s_%s_%s", type, tableCode, fieldName);
            builder.eq("CODEGENSEQ_FUNCCODE", tableCode).eq("CODEGENSEQ_TYPE", "TABLE");
        } else {
            seqKey = String.format("%s_%s_%s", type, funcCode, fieldName);
            builder.eq("CODEGENSEQ_FUNCCODE", funcCode);
        }
        if (StringUtil.isNotEmpty(zhId)) {
            builder.eq("SY_TENANT_ID", zhId);
        }

        try {
            Boolean locked = RedisLockUtils.getLock(LOCK_KEY+seqKey,seqKey,5L);
            if(!locked){
                throw new PlatformException("当前操作存在抢占编码操作，请重试",PlatformExceptionEnum.UNKOWN_ERROR);
            }
            //查询自增序列
            List<DynaBean> seqs = metaService.select("JE_CORE_CODEGENSEQ",
                    //暂时可以不设置周期 and CODEGENSEQ_EMPTYTYPE='"+cycle+"'
                    builder.eq("CODEGENSEQ_FIELDNAME", fieldName).eq("CODEGENSEQ_CODEBASE", codeBase));

            boolean isCreate = true;
            DynaBean seq = null;
            if (seqs.size() == 1) {
                //取一个
                seq = seqs.get(0);
                isCreate = false;
            } else if (!seqs.isEmpty()) {
                //取最大值序列
                seq = seqs.stream().collect(Collectors.collectingAndThen(Collectors.reducing((c1, c2) -> {
                    // 比较 CODEGENSEQ_CODEVALUE, CODEGENSEQ_LASTEMPTYTIME
                    int i = Integer.parseInt(c1.getStr("CODEGENSEQ_CODEVALUE")) - Integer.parseInt(c2.getStr("CODEGENSEQ_CODEVALUE"));
                    return i > 0 ? c1 : (i < 0 ? c2 : (c1.getStr("CODEGENSEQ_LASTEMPTYTIME").compareTo(c2.getStr("CODEGENSEQ_LASTEMPTYTIME")) > 0 ? c1 : c2));
                }), Optional::get));
                //清理无用的SEQ
                metaService.delete("JE_CORE_CODEGENSEQ",
                        //在原有的where条件基础上 排除当前使用的 seq
                        builder.clone().ne("JE_CORE_CODEGENSEQ_ID", seq.getStr("JE_CORE_CODEGENSEQ_ID")));
                isCreate = false;
            }

            //计算结果
            Integer codeValue = null;
            Integer oldCodeValue = null;
            //如果没有序列
            if (isCreate) {
                codeValue = Integer.parseInt(codeBase);
                seq = new DynaBean("JE_CORE_CODEGENSEQ", true);
            } else {
                oldCodeValue = seq.getInt("CODEGENSEQ_CODEVALUE");
                Integer codeStep = 1;
                if (StringUtil.isNumber(step)) {
                    codeStep = Integer.parseInt(step);
                }
                codeValue = oldCodeValue + codeStep;
            }

            //检验数值长度是否超长
            String codeValueStr = Integer.toString(codeValue);
            if (Integer.toString(codeValue).length() > length) {
                throw new PlatformException("当前编号长度大于了规定长度，请查看!", PlatformExceptionEnum.JE_CORE_SEQCODE_TOLONG_ERROR, new Object[]{infos, fieldName, codeBase, step, cycle, length});
            }

            //设置字段值
            seq.set("CODEGENSEQ_TYPE", type);
            seq.set("CODEGENSEQ_FIELDNAME", fieldName);
            seq.setInt("CODEGENSEQ_CODEVALUE", codeValue);
            if ("TABLE".equals(type)) {
                seq.set("CODEGENSEQ_FUNCNAME", tableName);
                seq.set("CODEGENSEQ_FUNCCODE", tableCode);
            } else {
                seq.set("CODEGENSEQ_FUNCID", funcId);
                seq.set("CODEGENSEQ_FUNCNAME", funcName);
                seq.set("CODEGENSEQ_FUNCCODE", funcCode);
            }
            seq.set("CODEGENSEQ_STEP", step);
            seq.set("CODEGENSEQ_EMPTYTYPE", cycle);
            seq.set("CODEGENSEQ_CODEBASE", codeBase);
            seq.set("CODEGENSEQ_CODEVALUE", codeValue);

            //租户信息
            if (StringUtil.isNotEmpty(zhId)) {
                seq.set("SY_TENANT_ID", zhId);
            }

            //保存或修改数据
            if (isCreate) {
                seq.set("CODEGENSEQ_LASTEMPTYTIME", DateUtils.formatDateTime(new Date()));
                //查询是否已经存在
                long count = metaService.countBySql(builder.table("JE_CORE_CODEGENSEQ"));
                if (count > 0) {
                    //如果已经存在重新进入方法
                    return getSeq(infos, fieldName, codeBase, step, cycle, length, zhId);
                }
                commonService.buildModelCreateInfo(seq);
                metaService.insert(seq);
            } else {
                seq.set(BeanService.KEY_TABLE_CODE, "JE_CORE_CODEGENSEQ");
                commonService.buildModelModifyInfo(seq);
                //CompareAndSwap 模拟CAS操作解决部分程度的分布式问题
                int update = metaService.update(seq, ConditionsWrapper.builder()
                        // where JE_CORE_CODEGENSEQ_ID = xxx and CODEGENSEQ_CODEVALUE =  oldCodeValue
                        .eq("JE_CORE_CODEGENSEQ_ID", seq.getStr("JE_CORE_CODEGENSEQ_ID"))
                        .eq("CODEGENSEQ_CODEVALUE", oldCodeValue));
                //如果更新失败两种可能，1-当前seq被另一个分布服务清理掉，2- 当前seq被另一个分布服务使用 oldCodeValue 已经改变了
                if (update == 0) {
                    //如果未更新成功重新进入方法
                    return getSeq(infos, fieldName, codeBase, step, cycle, length, zhId);
                }
            }

            //缺位填充
            if (codeValueStr.length() != length) {
                return StringUtil.preFillUp(codeValueStr, length, '0');
            } else {
                return codeValueStr;
            }
        } catch (Exception e) {
            e.printStackTrace();
            logger.error(e.getMessage());
            throw new PlatformException(e.getMessage(), PlatformExceptionEnum.UNKOWN_ERROR);
        } finally {
            RedisLockUtils.releaseLock(LOCK_KEY + seqKey);
        }
    }
}
